package com.zg.autoform.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zg.common.core.dao.autoform.DiyImportErrorDao;
import com.zg.common.core.dao.autoform.DiyImportRecordDao;
import com.zg.common.core.dao.autoform.DiyImportError;
import com.zg.common.core.dao.autoform.DiyImportRecord;
import com.zg.common.core.dao.autoform.DiyImportTemplate;
import com.zg.autoform.dto.ImportErrorDetail;
import com.zg.autoform.dto.ImportFieldConfig;
import com.zg.autoform.dto.DuplicateCheckResult;
import com.zg.autoform.enm.FieldTypeEnum;
import com.zg.autoform.enm.DuplicateHandleStrategyEnum;
import com.zg.autoform.enm.ImportStatusEnum;
import com.zg.autoform.service.ImportDataService;
import com.zg.autoform.service.ImportTemplateService;
import com.zg.autoform.service.DuplicateCheckService;
import com.zg.autoform.web.vo.ImportProgressVo;
import com.zg.autoform.web.vo.ImportResultVo;
import com.zg.common.core.A;
import com.zg.common.core.dao.autoform.DiyForm;
import com.zg.common.core.dao.autoform.DiyFormDao;
import com.zg.common.web.Req;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 数据导入服务实现
 * @author zg
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ImportDataServiceImpl implements ImportDataService {

    private final ImportTemplateService templateService;
    private final DiyFormDao formDao;
    private final DiyImportRecordDao recordDao;
    private final DiyImportErrorDao errorDao;
    private final JdbcTemplate jdbcTemplate;
    private final DuplicateCheckService duplicateCheckService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String PROGRESS_KEY_PREFIX = "import:progress:";
    private static final int BATCH_SIZE = 1000;

    @Override
    public ImportResultVo importDataSync(MultipartFile file, Long templateId) {
        String batchNo = IdUtil.simpleUUID();
        long startTime = System.currentTimeMillis();

        try {
            // 1. 创建导入记录
            DiyImportRecord record = createImportRecord(file, templateId, batchNo);

            // 2. 解析和处理数据
            ImportProcessResult result = processImportData(file, templateId, batchNo, false);

            // 3. 更新导入记录
            updateImportRecord(record, result, startTime);

            // 4. 构建返回结果
            return buildImportResult(record, result, false);

        } catch (Exception e) {
            log.error("同步导入数据失败, batchNo: {}", batchNo, e);
            updateImportRecordError(batchNo, e.getMessage(), startTime);
            throw new RuntimeException("导入失败: " + e.getMessage());
        }
    }

    @Override
    @Async("importTaskExecutor")
    public ImportResultVo importDataAsync(MultipartFile file, Long templateId) {
        String batchNo = IdUtil.simpleUUID();
        long startTime = System.currentTimeMillis();

        try {
            // 1. 创建导入记录
            DiyImportRecord record = createImportRecord(file, templateId, batchNo);

            // 2. 初始化进度
            updateProgress(batchNo, ImportStatusEnum.PROCESSING.name(), 0, "开始处理", "正在解析文件...");

            // 3. 解析和处理数据
            ImportProcessResult result = processImportData(file, templateId, batchNo, true);

            // 4. 更新导入记录
            updateImportRecord(record, result, startTime);

            // 5. 完成进度
            updateProgress(batchNo, result.getStatus(), 100, "完成", "导入处理完成");

            return buildImportResult(record, result, true);

        } catch (Exception e) {
            log.error("异步导入数据失败, batchNo: {}", batchNo, e);
            updateImportRecordError(batchNo, e.getMessage(), startTime);
            updateProgress(batchNo, ImportStatusEnum.FAILED.name(), -1, "失败", "导入失败: " + e.getMessage());
            throw new RuntimeException("导入失败: " + e.getMessage());
        }
    }

    @Override
    public ImportProgressVo getImportProgress(String batchNo) {
        String key = PROGRESS_KEY_PREFIX + batchNo;
        ImportProgressVo progress = (ImportProgressVo) redisTemplate.opsForValue().get(key);
        return progress != null ? progress : new ImportProgressVo();
    }

    @Override
    public void updateProgress(String batchNo, String status, Integer progress, String stage, String message) {
        updateProgress(batchNo, status, progress, stage, message, null, null, null, null);
    }

    @Override
    public void updateProgress(String batchNo, String status, Integer progress, String stage, String message,
                              Integer totalRows, Integer processedRows, Integer successRows, Integer failRows) {
        ImportProgressVo progressVo = new ImportProgressVo();
        progressVo.setBatchNo(batchNo);
        progressVo.setStatus(status);
        progressVo.setProgress(progress);
        progressVo.setStage(stage);
        progressVo.setMessage(message);
        progressVo.setTotalRows(totalRows);
        progressVo.setProcessedRows(processedRows);
        progressVo.setSuccessRows(successRows);
        progressVo.setFailRows(failRows);
        progressVo.setUpdateTime(new Date());

        String key = PROGRESS_KEY_PREFIX + batchNo;
        redisTemplate.opsForValue().set(key, progressVo, 2, TimeUnit.HOURS);

        // TODO: 通过WebSocket推送进度更新
        // webSocketService.sendProgress(batchNo, progressVo);
    }

    /**
     * 创建导入记录
     */
    private DiyImportRecord createImportRecord(MultipartFile file, Long templateId, String batchNo) {
        DiyImportRecord record = new DiyImportRecord();
        record.setTemplateId(templateId);
        record.setBatchNo(batchNo);
        record.setFileName(file.getOriginalFilename());
        record.setFileSize(file.getSize());
        record.setStatus(ImportStatusEnum.PROCESSING.name());
        record.setStartTime(new Date());
        recordDao.save(record);
        return record;
    }

    /**
     * 处理导入数据的核心逻辑
     */
    private ImportProcessResult processImportData(MultipartFile file, Long templateId, String batchNo, boolean async) {
        // 1. 获取模板配置
        DiyImportTemplate template = templateService.getTemplateById(templateId);
        A.isInDatabase(template, "模板不存在");

        DiyForm form = formDao.getById(template.getFormId());
        A.isInDatabase(form, "表单不存在");

        List<ImportFieldConfig> fields = templateService.getTemplateFieldConfigs(template.getId());

        // 2. 解析Excel数据
        if (async) {
            updateProgress(batchNo, ImportStatusEnum.PROCESSING.name(), 20, "解析数据", "正在读取Excel文件...");
        }

        ImportDataProcessor processor = new ImportDataProcessor(fields, template, form, batchNo, async);

        try {
            // 使用原生POI读取Excel
            Workbook workbook = null;
            try {
                // 尝试读取为XLSX格式
                workbook = new XSSFWorkbook(file.getInputStream());
            } catch (Exception e) {
                // 如果失败，尝试读取为XLS格式
                try {
                    workbook = new HSSFWorkbook(file.getInputStream());
                } catch (Exception ex) {
                    throw new RuntimeException("不支持的Excel文件格式: " + ex.getMessage());
                }
            }

            Sheet sheet = workbook.getSheetAt(0);
            processor.processSheet(sheet);
            workbook.close();

        } catch (Exception e) {
            throw new RuntimeException("读取Excel文件失败: " + e.getMessage());
        }

        return processor.getResult();
    }

    /**
     * 内部类：Excel数据处理器
     */
    private class ImportDataProcessor {

        private final List<ImportFieldConfig> fields;
        private final DiyImportTemplate template;
        private final DiyForm form;
        private final String batchNo;
        private final boolean async;

        private final List<Map<String, Object>> validDataList = new ArrayList<>();
        private final List<ImportErrorDetail> errorList = new ArrayList<>();
        private int totalRows = 0;
        private int processedRows = 0;
        private int duplicateSkippedRows = 0;
        private int duplicateOverwrittenRows = 0;

        public ImportDataProcessor(List<ImportFieldConfig> fields, DiyImportTemplate template,
                                DiyForm form, String batchNo, boolean async) {
            this.fields = fields;
            this.template = template;
            this.form = form;
            this.batchNo = batchNo;
            this.async = async;
        }

        public void processSheet(Sheet sheet) {
            int lastRowNum = sheet.getLastRowNum();

            // 从第4行开始读取数据（跳过前3行标题）
            for (int rowIndex = 3; rowIndex <= lastRowNum; rowIndex++) {
                Row row = sheet.getRow(rowIndex);
                if (row == null) {
                    continue;
                }

                totalRows++;
                int currentRow = rowIndex + 1;

                // 验证和转换数据
                Map<String, Object> convertedData = new HashMap<>();
                List<String> rowErrors = new ArrayList<>();

                for (int i = 0; i < fields.size(); i++) {
                    ImportFieldConfig field = fields.get(i);
                    String cellValue = getCellValue(row.getCell(i));

                    try {
                        Object convertedValue = validateAndConvertValue(cellValue, field, currentRow);
                        convertedData.put(field.getFieldName(), convertedValue);
                    } catch (Exception e) {
                        rowErrors.add(e.getMessage());

                        ImportErrorDetail error = new ImportErrorDetail();
                        error.setRowNum(currentRow);
                        error.setFieldName(field.getFieldName());
                        error.setFieldLabel(field.getFieldLabel());
                        error.setOriginalValue(cellValue);
                        error.setErrorType("VALIDATION_ERROR");
                        error.setErrorMessage(e.getMessage());
                        error.setRowData(convertRowDataToMap(row));
                        errorList.add(error);
                    }
                }

                if (rowErrors.isEmpty()) {
                    // 处理重复数据检查
                    log.debug("第{}行开始处理重复数据检查，数据: {}", currentRow, convertedData);
                    boolean shouldAddData = handleDuplicateData(convertedData, currentRow);
                    log.debug("第{}行重复数据检查结果: shouldAddData = {}", currentRow, shouldAddData);

                    if (shouldAddData) {
                        // 添加系统字段
                        convertedData.put("id", IdUtil.getSnowflake().nextId());
                        convertedData.put("create_time", new Date());
                        convertedData.put("create_by", getCurrentUsername());
                        convertedData.put("update_time", new Date());
                        convertedData.put("update_by", getCurrentUsername());
                        convertedData.put("ver", 1);
                        convertedData.put("is_del", false);

                        validDataList.add(convertedData);
                        log.debug("第{}行数据已添加到待插入列表", currentRow);
                    } else {
                        log.debug("第{}行数据不需要插入（已跳过或已覆盖）", currentRow);
                    }
                }

                processedRows++;

                // 批量处理
                if (validDataList.size() >= BATCH_SIZE) {
                    saveBatchData();
                }

                // 更新进度
                if (async && processedRows % 50 == 0) {
                    int progress = Math.min(90, 20 + (processedRows * 60 / Math.max(1, lastRowNum - 2)));
                    updateProgress(batchNo, ImportStatusEnum.PROCESSING.name(), progress,
                            "数据处理", String.format("已处理 %d 行数据", processedRows));
                }
            }

            // 处理剩余数据
            if (!validDataList.isEmpty()) {
                saveBatchData();
            }

            // 保存错误信息
            saveErrorDetails();

            if (async) {
                updateProgress(batchNo, ImportStatusEnum.PROCESSING.name(), 90, "完成保存", "正在整理结果...");
            }
        }

        private String getCellValue(Cell cell) {
            if (cell == null) {
                return "";
            }

            switch (cell.getCellType()) {
                case STRING:
                    return cell.getStringCellValue();
                case NUMERIC:
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return cell.getDateCellValue().toString();
                    } else {
                        // 避免科学计数法
                        double numericValue = cell.getNumericCellValue();
                        if (numericValue == (long) numericValue) {
                            return String.valueOf((long) numericValue);
                        } else {
                            return String.valueOf(numericValue);
                        }
                    }
                case BOOLEAN:
                    return String.valueOf(cell.getBooleanCellValue());
                case FORMULA:
                    return cell.getCellFormula();
                default:
                    return "";
            }
        }

        private Map<String, Object> convertRowDataToMap(Row row) {
            Map<String, Object> result = new HashMap<>();
            for (int i = 0; i < fields.size(); i++) {
                ImportFieldConfig field = fields.get(i);
                String value = getCellValue(row.getCell(i));
                result.put(field.getFieldName(), value);
            }
            return result;
        }

        /**
         * 处理重复数据
         * @param data 数据
         * @param rowNum 行号
         * @return 是否应该添加数据
         */
        private boolean handleDuplicateData(Map<String, Object> data, int rowNum) {
            // 获取处理策略，默认为ALLOW
            String strategy = template.getDuplicateHandleStrategy();
            if (StrUtil.isBlank(strategy)) {
                strategy = DuplicateHandleStrategyEnum.ALLOW.name();
            }

            // 如果策略是ALLOW，直接返回true，不进行重复检查
            if (DuplicateHandleStrategyEnum.ALLOW.name().equals(strategy)) {
                return true;
            }

            // 如果没有配置检查字段，直接返回true
            if (StrUtil.isBlank(template.getDuplicateCheckFields())) {
                return true;
            }

            // 解析检查字段
            List<String> checkFields = Arrays.asList(template.getDuplicateCheckFields().split(","))
                .stream()
                .map(String::trim)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toList());

            if (checkFields.isEmpty()) {
                return true;
            }

            // 检查重复数据
            DuplicateCheckResult checkResult = duplicateCheckService.checkDuplicate(
                form.getCode(), data, checkFields);

            if (!checkResult.getIsDuplicate()) {
                return true;
            }

            // 根据处理策略处理重复数据
            switch (DuplicateHandleStrategyEnum.valueOf(strategy)) {
                case SKIP:
                    // 跳过重复数据
                    duplicateSkippedRows++;
                    log.debug("跳过重复数据，行号: {}, 重复原因: {}", rowNum, checkResult.getDuplicateReason());
                    return false;

                case OVERWRITE:
                    // 覆盖重复数据
                    try {
                        log.info("开始覆盖重复数据，行号: {}, ID: {}, 数据: {}", rowNum, checkResult.getDuplicateId(), data);
                        duplicateCheckService.updateDuplicateData(form.getCode(),
                            checkResult.getDuplicateId(), data);
                        duplicateOverwrittenRows++;
                        log.info("覆盖重复数据成功，行号: {}, ID: {}, 返回false不插入新数据", rowNum, checkResult.getDuplicateId());
                        return false; // 已经更新了，不需要插入新数据
                    } catch (Exception e) {
                        log.error("覆盖重复数据失败，行号: {}, 错误: {}", rowNum, e.getMessage(), e);
                        addDuplicateError(rowNum, checkResult, "覆盖重复数据失败: " + e.getMessage());
                        return false;
                    }

                case ERROR:
                    // 报错停止导入
                    addDuplicateError(rowNum, checkResult, "发现重复数据，停止导入");
                    throw new RuntimeException(String.format("第%d行发现重复数据: %s", rowNum, checkResult.getDuplicateReason()));

                case ALLOW:
                    // 允许重复数据（不应该到这里，因为前面已经处理了）
                    return true;

                default:
                    // 默认跳过
                    duplicateSkippedRows++;
                    return false;
            }
        }

        /**
         * 添加重复数据错误信息
         */
        private void addDuplicateError(int rowNum, DuplicateCheckResult checkResult, String errorMessage) {
            ImportErrorDetail error = new ImportErrorDetail();
            error.setRowNum(rowNum);
            error.setFieldName(checkResult.getCheckFields());
            error.setFieldLabel("重复检查字段");
            error.setOriginalValue(checkResult.getDuplicateFields().toString());
            error.setErrorType("DUPLICATE_ERROR");
            error.setErrorMessage(errorMessage);
            error.setRowData(checkResult.getDuplicateFields());
            errorList.add(error);
        }



        private void saveBatchData() {
            if (validDataList.isEmpty()) {
                return;
            }

            try {
                String tableName = form.getCode();
                batchInsertData(tableName, validDataList);
                validDataList.clear();
            } catch (Exception e) {
                log.error("批量保存数据失败", e);
                throw new RuntimeException("保存数据失败: " + e.getMessage());
            }
        }

        private void saveErrorDetails() {
            if (errorList.isEmpty()) {
                return;
            }

            List<DiyImportError> errorEntities = new ArrayList<>();
            for (ImportErrorDetail error : errorList) {
                DiyImportError entity = new DiyImportError();
                entity.setBatchNo(batchNo);
                entity.setRowNum(error.getRowNum());
                entity.setFieldName(error.getFieldName());
                entity.setFieldLabel(error.getFieldLabel());
                entity.setOriginalValue(error.getOriginalValue());
                entity.setErrorType(error.getErrorType());
                entity.setErrorMessage(error.getErrorMessage());
                entity.setRowDataJson(JSONUtil.toJsonStr(error.getRowData()));
                errorEntities.add(entity);
            }

            errorDao.saveBatch(errorEntities);
        }

        public ImportProcessResult getResult() {
            // 重新计算成功和失败数量，考虑重复数据处理
            int actualSuccessCount = totalRows - errorList.size() - duplicateSkippedRows;
            int failCount = errorList.size();

            String status;
            if (failCount == 0 && duplicateSkippedRows == 0) {
                status = ImportStatusEnum.SUCCESS.name();
            } else if (actualSuccessCount == 0) {
                status = ImportStatusEnum.FAILED.name();
            } else {
                status = ImportStatusEnum.PARTIAL.name();
            }

            ImportProcessResult result = new ImportProcessResult();
            result.setTotalRows(totalRows);
            result.setSuccessRows(actualSuccessCount + duplicateOverwrittenRows);
            result.setFailRows(failCount);
            result.setStatus(status);
            result.setHasErrors(failCount > 0);
            result.setDuplicateSkippedRows(duplicateSkippedRows);
            result.setDuplicateOverwrittenRows(duplicateOverwrittenRows);

            return result;
        }
    }

    /**
     * 验证和转换字段值
     */
    private Object validateAndConvertValue(String value, ImportFieldConfig field, int rowNum) {
        // 处理空值
        if (StrUtil.isBlank(value)) {
            if (field.getRequired() != null && field.getRequired()) {
                throw new RuntimeException(field.getFieldLabel() + "不能为空");
            }
            return field.getDefaultValue();
        }

        // 根据字段类型转换
        try {
            switch (FieldTypeEnum.valueOf(field.getFieldType())) {
                case STRING:
                    return validateString(value, field);
                case INTEGER:
                    return Integer.valueOf(value.trim());
                case DECIMAL:
                    return new BigDecimal(value.trim());
                case DATE:
                    return parseDate(value.trim(), "yyyy-MM-dd");
                case DATETIME:
                    return parseDate(value.trim(), "yyyy-MM-dd HH:mm:ss");
                case BOOLEAN:
                    return parseBoolean(value.trim());
                default:
                    return value;
            }
        } catch (Exception e) {
            throw new RuntimeException(field.getFieldLabel() + "格式错误: " + e.getMessage());
        }
    }

    private String validateString(String value, ImportFieldConfig field) {
        if ("REGEX".equals(field.getValidationRule()) && StrUtil.isNotBlank(field.getValidationParam())) {
            if (!Pattern.matches(field.getValidationParam(), value)) {
                throw new RuntimeException(field.getErrorMessage() != null ? field.getErrorMessage() : "格式不正确");
            }
        }

        if ("LENGTH".equals(field.getValidationRule()) && StrUtil.isNotBlank(field.getValidationParam())) {
            String[] parts = field.getValidationParam().split(",");
            if (parts.length == 2) {
                int min = Integer.parseInt(parts[0]);
                int max = Integer.parseInt(parts[1]);
                if (value.length() < min || value.length() > max) {
                    throw new RuntimeException(String.format("长度必须在%d-%d之间", min, max));
                }
            }
        }

        return value;
    }

    private Date parseDate(String value, String pattern) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
            return sdf.parse(value);
        } catch (ParseException e) {
            throw new RuntimeException("日期格式错误，正确格式：" + pattern);
        }
    }

    private Boolean parseBoolean(String value) {
        if ("true".equalsIgnoreCase(value) || "1".equals(value) || "是".equals(value)) {
            return true;
        } else if ("false".equalsIgnoreCase(value) || "0".equals(value) || "否".equals(value)) {
            return false;
        } else {
            throw new RuntimeException("布尔值格式错误，支持：true/false、1/0、是/否");
        }
    }



    private void batchInsertData(String tableName, List<Map<String, Object>> dataList) {
        if (dataList.isEmpty()) {
            return;
        }

        Map<String, Object> firstRow = dataList.get(0);
        List<String> columns = new ArrayList<>(firstRow.keySet());

        // Oracle兼容的批量插入SQL
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO ").append(tableName).append(" (");
        sql.append(String.join(", ", columns));
        sql.append(") VALUES (");

        List<String> placeholders = new ArrayList<>();
        for (int i = 0; i < columns.size(); i++) {
            placeholders.add("?");
        }
        sql.append(String.join(", ", placeholders));
        sql.append(")");

        // 使用JdbcTemplate的batchUpdate方法
        List<Object[]> batchArgs = new ArrayList<>();
        for (Map<String, Object> row : dataList) {
            Object[] args = new Object[columns.size()];
            for (int i = 0; i < columns.size(); i++) {
                args[i] = row.get(columns.get(i));
            }
            batchArgs.add(args);
        }

        jdbcTemplate.batchUpdate(sql.toString(), batchArgs);
    }

    private String getCurrentUsername() {
        try {
            return Req.getCurrUser().getUsername();
        } catch (Exception e) {
            return "system";
        }
    }

    private void updateImportRecord(DiyImportRecord record, ImportProcessResult result, long startTime) {
        record.setTotalRows(result.getTotalRows());
        record.setSuccessRows(result.getSuccessRows());
        record.setFailRows(result.getFailRows());
        record.setStatus(result.getStatus());
        record.setEndTime(new Date());
        record.setDuration(System.currentTimeMillis() - startTime);
        recordDao.updateById(record);
    }

    private void updateImportRecordError(String batchNo, String errorMessage, long startTime) {
        DiyImportRecord record = recordDao.getOne(
            new LambdaQueryWrapper<DiyImportRecord>().eq(DiyImportRecord::getBatchNo, batchNo)
        );
        if (record != null) {
            record.setStatus(ImportStatusEnum.FAILED.name());
            record.setErrorMessage(errorMessage);
            record.setEndTime(new Date());
            record.setDuration(System.currentTimeMillis() - startTime);
            recordDao.updateById(record);
        }
    }

    private ImportResultVo buildImportResult(DiyImportRecord record, ImportProcessResult result, boolean async) {
        ImportResultVo vo = new ImportResultVo();
        vo.setBatchNo(record.getBatchNo());
        vo.setAsync(async);
        vo.setTotalRows(result.getTotalRows());
        vo.setSuccessRows(result.getSuccessRows());
        vo.setFailRows(result.getFailRows());
        vo.setStatus(result.getStatus());
        vo.setDuration(record.getDuration());
        vo.setHasErrors(result.getHasErrors());
        vo.setDuplicateSkippedRows(result.getDuplicateSkippedRows());
        vo.setDuplicateOverwrittenRows(result.getDuplicateOverwrittenRows());
        return vo;
    }

    /**
     * 导入处理结果内部类
     */
    private static class ImportProcessResult {
        private Integer totalRows;
        private Integer successRows;
        private Integer failRows;
        private String status;
        private Boolean hasErrors;
        private Integer duplicateSkippedRows = 0;
        private Integer duplicateOverwrittenRows = 0;

        // getters and setters
        public Integer getTotalRows() { return totalRows; }
        public void setTotalRows(Integer totalRows) { this.totalRows = totalRows; }
        public Integer getSuccessRows() { return successRows; }
        public void setSuccessRows(Integer successRows) { this.successRows = successRows; }
        public Integer getFailRows() { return failRows; }
        public void setFailRows(Integer failRows) { this.failRows = failRows; }
        public String getStatus() { return status; }
        public void setStatus(String status) { this.status = status; }
        public Boolean getHasErrors() { return hasErrors; }
        public void setHasErrors(Boolean hasErrors) { this.hasErrors = hasErrors; }
        public Integer getDuplicateSkippedRows() { return duplicateSkippedRows; }
        public void setDuplicateSkippedRows(Integer duplicateSkippedRows) { this.duplicateSkippedRows = duplicateSkippedRows; }
        public Integer getDuplicateOverwrittenRows() { return duplicateOverwrittenRows; }
        public void setDuplicateOverwrittenRows(Integer duplicateOverwrittenRows) { this.duplicateOverwrittenRows = duplicateOverwrittenRows; }
    }
}
